#include <stdio.h>
#include <limits.h>
#include <unistd.h>
#include <wait.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "xaux_object.h"

/* workaround for "stored reference to aux_t is corrupred" problem */
static void (*aux_setvalue)(aux_t *, const unsigned char *, int);
static unsigned char *(*compose)(const aux_data_t *, int *);
static aux_t aux_tmp;

static Bool xaux_object_event_filter(Display *display, Window window, XEvent *event, XPointer pointer);
static Bool xaux_object_get_extwin(xaux_class_t *xc, Display *display);
static Bool xaux_object_launch_ext(xaux_class_t *xc, Display *display);

static Bool xaux_object_init_class(Display *display, Window window, xaux_class_t *xc);
void xaux_object_print(xaux_object_t *xaux_object);

extern xaux_class_t xaux_classes[];

xaux_object_t *xaux_object_new()
{
	xaux_object_t *xaux_object;

	xaux_object = (xaux_object_t *)calloc(1, sizeof(xaux_object_t));
	if (xaux_object == NULL)
		return NULL;

	return xaux_object;
}

Bool xaux_object_init(xaux_object_t *xaux_object, aux_t *aux, char *classname, xaux_class_t *xaux_classes)
{
	Display *display;
	xaux_class_t *p;

	if (!aux)
		return False;

	if (!classname || !*classname)
		return False;

	if (!xaux_classes)
		return False;

	/* workaround for "stored reference to aux_t is corrupred" problem */
	aux_tmp.ic = aux->ic;
	aux_setvalue = aux->service->aux_setvalue;
	compose = aux->service->compose;

	display = aux->service->display(aux);

	xaux_object->display = display;

	xaux_object->classname = classname;
	xaux_object->atom_classname = XInternAtom(display, classname, False);

	xaux_object->window = XCreateSimpleWindow(display, RootWindow(display, 0),
		     0, 0, 1, 1, 0, 0, 0);

	if (xaux_object->window == None) {
		DEBUG_printf("xaux_object_new: creating window failed.\n");
		return False;
	}

	XSetSelectionOwner(display,
		xaux_object->atom_classname, xaux_object->window, CurrentTime);

        XSelectInput(display, xaux_object->window, PropertyChangeMask);

        aux->service->register_X_filter(display, xaux_object->window,
		ClientMessage, ClientMessage,
		xaux_object_event_filter, NULL);

	xaux_object->xaux_classes = xaux_classes;
	p = xaux_classes;
	while(p->classname != NULL) {
		xaux_object_init_class(display, xaux_object->window, p);
		p++;
	}

	xaux_object_print(xaux_object);

	return (True);
}

void xaux_object_destroy(xaux_object_t *xaux_object)
{
	if (xaux_object == NULL ||
	    xaux_object->display ||
	    xaux_object->window == None)
		return;

	XDestroyWindow(xaux_object->display, xaux_object->window);

	return;
}

void xaux_object_print(xaux_object_t *xaux_object)
{
#ifdef DEBUG
	xaux_class_t *p;

	if (xaux_object == NULL ||
	    xaux_object->display ||
	    xaux_object->window == None)
		return;

	printf("xaux_object: %p\n", xaux_object);
	printf("  classname: %s\n", xaux_object->classname);
	printf("  window: %d\n", xaux_object->window);

	p = xaux_object->xaux_classes;
	while(p->classname != NULL) {
		printf(" class: %s\n", p->classname);
		p++;
	}
#endif
	return;
}

/************************************************************
    functions to process xaux_class_t and aux messages
************************************************************/
static Bool xaux_object_init_class(Display *display, Window window, xaux_class_t *xc)
{
	char		buf[XAUX_MAXCLASSNAMELEN + XAUX_MAXSUFFIXLEN + 1];
	int		i;
	int		len;

	if (xc == NULL)
		return False;

	DEBUG_printf("xaux_object_init_class ===\n");
	if (access(xc->extexec, X_OK) != 0) {
		DEBUG_printf("executable \"%s\" not found\n", xc->extexec);
		return False;
	}

	if (xc->classname == NULL) {
		return False;
	}

	xc->atom_classname = XInternAtom(display, xc->classname, False);

	len = XAUX_MAXCLASSNAMELEN + XAUX_MAXSUFFIXLEN + 1;
	snprintf(buf, len, "%s%s", xc->classname, XAUX_SOWIN_SUFFIX);
	xc->atom_sowin = XInternAtom(display, buf, False);

	snprintf(buf, len, "%s%s", xc->classname, XAUX_EXTWIN_SUFFIX);
	xc->atom_extwin = XInternAtom(display, buf, False);

	for (i = 0; i < XAUX_SX_NATOMS; i++) {
		snprintf(buf, len, "%s%s_%d",
			xc->classname, XAUX_SX_SUFFIX, i);
		xc->atom_sx[i] = XInternAtom(display, buf, False);
	}
	xc->atom_sx_idx = 1;

	for (i = 0; i < XAUX_XS_NATOMS; i++) {
		snprintf(buf, len, "%s%s_%d",
			xc->classname, XAUX_XS_SUFFIX, i);
		xc->atom_xs[i] = XInternAtom(display, buf, False);
	}
	xc->atom_xs_idx = 1;

	xc->extwin = (Window)0;

	if (xaux_object_launch_ext(xc, display) == False)
		return (False);

	i = 0;
	while (xaux_object_get_extwin(xc, display) == False) {
		DEBUG_printf("classname: %s, retry number: %d, sleep: %d um\n", xc->classname, i, XAUX_RETRYINT_EXTWIN);
		if (i++ > XAUX_MAXRETRY_EXTWIN)
			break;
		usleep(XAUX_RETRYINT_EXTWIN);
	}

	return True;
}

char *xaux_object_get_classname_from_utfname(CARD16 *utf_name, int utf_name_len)
{
	int i;
	char *classname = NULL;
	int num_chars = utf_name_len / sizeof(CARD16);

	if (!utf_name || !*utf_name)
		return (NULL);

	classname = (char *)calloc(num_chars + 1, sizeof(char));
	if (classname == NULL) return (NULL);

	for (i = 0; i < num_chars; i++) {
		classname[i] = utf_name[i] & 0xff;
	}

	return (classname);
}

static xaux_class_t *xaux_object_get_class_from_name(xaux_object_t *xaux_object, char *classname)
{
	xaux_class_t *aux_class;
	Display *display;
	Window owner;

	Atom atom_classname = (Atom)None;

	if (xaux_object == NULL || classname == NULL)
		return (NULL);

	display = xaux_object->display;
	atom_classname = XInternAtom(display, classname, True);

	/* check whether the classname is registered by the owner */
	DEBUG_printf("classname: %s, atom_classname: %p\n", classname, atom_classname);
	if (atom_classname == (Atom)None)
		return (NULL);

	/* check whether the classname is for the binary aux module */
	for (aux_class = xaux_object->xaux_classes; aux_class->classname; aux_class++) {
		if (!strcasecmp(classname, aux_class->classname))
			return (aux_class);
	}

	/* check whether the classname is launched by an aux manager */
	owner = XGetSelectionOwner(display, atom_classname);
	if (owner == (Window)None)
		return (NULL);

	for (aux_class = xaux_object->xaux_classes; aux_class->classname; aux_class++) {
		if (aux_class->extwin == owner)
			return (aux_class);
	}

	return (NULL);
}

static Bool xaux_object_get_extwin(xaux_class_t *xc, Display *display)
{
	if (xc->atom_extwin == (Atom)None)
		return False;

	xc->extwin = XGetSelectionOwner(display, xc->atom_extwin);
	if (xc->extwin == None) {
		return False;
	}
	return True;
}

static void xaux_object_signal_child_handler(int sig, siginfo_t * info, void * ucontext)
{
    pid_t   pid;
    int     status;

    while ((pid = waitpid(info->si_pid, &status, WNOHANG|WUNTRACED)) > 0) {
        printf("pid %d: die\n", pid);
    }
}

static void xaux_object_register_signal_child_handler()
{
    struct sigaction act;

    act.sa_handler = NULL;
    act.sa_sigaction = xaux_object_signal_child_handler;
    sigfillset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO;

    sigaction(SIGCHLD, &act, NULL);
}

static Bool xaux_object_launch_ext(xaux_class_t *xc, Display *display)
{
	pid_t		pid;

#ifdef  sun
	typedef void (*sighandler_t) (int);
	sighandler_t    ohdl;
#endif

	if (xc->atom_extwin == (Atom)None)
		return False;

	if (xaux_object_get_extwin(xc, display) == True) {
		return True;
	}

#ifdef	sun
	/* On solaris, if SIGCHLD's handler is not SIG_IGN, only the waitpid(3) inside 
         * the handler function can retrieve the child_process information. The outside 
         * one will return -1, even there is no waitpid called in the specified signal
         * handler, and the child_process will become <defunct>. The trick is to clean 
         * the handler temporarily.
         *  
         * BTW, from Linux signal(2) manual, according to POSIX (3.3.1.3), when SIGCHLD 
         * is set to SIG_IGN, the result is undefined, and may fail on Linux. */

	ohdl = signal (SIGCHLD, SIG_IGN);
	pid = fork1();
#else
	pid = fork();
#endif

	if (pid == (pid_t)(-1)) { /* fork failed */
		return False;
	} else if (0 == pid) { /* child */
		pid_t	dpid;
#ifdef  sun
		dpid = fork1 ();
#else
		dpid = fork ();
#endif
		if (dpid == (pid_t)(-1)) { /* create daemon failed */
			_exit (1);
		} else if (0 == dpid) { /* daemon process */
			setsid ();
			chdir("/tmp");
			umask (0);
			execl(xc->extexec, xc->classname, NULL);
			_exit(1);
		}
		
		/* parent of daemon */
		_exit (0);
	}
	
	/* parent, wait for the child process (father of daemon process) to exit */
	waitpid (pid, NULL, 0);

#ifdef  sun
	signal (SIGCHLD, ohdl);
#endif
	return True;
}

static char *xaux_object_compose_aux_data(aux_t *aux, xaux_class_t *xc, aux_data_t *aux_data, int type, int *string_len)
{
	XPoint		point;
	char *		string_buf;
	size_t		i;
	int		*ip;
	char		*sp;
	size_t		total;
	size_t		len;

	/* estimate enough size for string_buf */

	/* size for header */
	total = SX_SIZE_PROP_HEADER_DRAW;

	/* add size for integer_values */
	total += (sizeof (CARD32) * aux_data->integer_count);

	/* add size for string_values */
	if (aux_data->string_count > 0) {
		for (i = 0; i < aux_data->string_count; i++) {
			/* number of bytes */
			len = aux_data->string_list[i].length;

			/* consider padding */
			total +=
			((sizeof (CARD16) + len + 3) / 4) * 4;
		}
	}

	if ((string_buf = (char *)calloc(total, sizeof(char))) == NULL) {
		return NULL;
	}

	SX_PROP_ATOM_AUX_NAME(string_buf) = xc->atom_classname;
	SX_PROP_TYPE(string_buf) = type;
	SX_PROP_INDEX(string_buf) = xc->index;
	SX_PROP_IMID(string_buf) = aux_data->im;
	SX_PROP_ICID(string_buf) = aux_data->ic;
	SX_PROP_SOWIN(string_buf) = xc->sowin;

	SX_PROP_CLIENTWIN(string_buf) = aux->service->client_window(aux);

	aux->service->point(aux, &point);
	SX_PROP_POSX(string_buf) = point.x;
	SX_PROP_POSY(string_buf) = point.y;

	SX_PROP_FOCUSWIN(string_buf) = aux->service->window(aux);

	SX_PROP_INT_COUNT(string_buf) = aux_data->integer_count;
	SX_PROP_STR_COUNT(string_buf) = aux_data->string_count;

	ip = (int *)SX_PROP_INT_LIST(string_buf);

	if (aux_data->integer_count > 0) {
		for (i = 0; i < aux_data->integer_count; i++) {
			*ip++ = aux_data->integer_list[i];
		}
	}

	sp = (char *)SX_PROP_STR_LIST(string_buf);

	if (aux_data->string_count > 0) {
		char *		ob;
		size_t		obl;

		ob = sp;

		for (i = 0; i < aux_data->string_count; i++) {
			int		pn;
			unsigned char	*p;
			size_t		j;

			len = aux_data->string_list[i].length;
			p = aux_data->string_list[i].ptr;

			*(CARD16 *)ob = len;
			ob += sizeof (CARD16);

			for (j = 0; j < len; j++) {
				*ob++ = *p++;
			}
				
			pn = padding[(sizeof (CARD16) + len) % 4];

			/* padding */
			for (j = 0; j < pn; j++) {
				*ob++ = 0U;
			}
			sp = ob;
		}
	}

	*string_len = sp - &(string_buf[0]);
	return (string_buf);
}

aux_data_t *xaux_object_decompose_from_string(xaux_class_t *xc, unsigned char *string_buf)
{
	aux_data_t *	aux_data;
	int		i;

	aux_data = (aux_data_t *)calloc(1, sizeof(aux_data_t));
	if (aux_data == NULL)
		return (NULL);

	/* header */
	aux_data->type = AUX_DATA_SETVALUE;
	aux_data->im = XS_PROP_IMID(string_buf);
	aux_data->ic = XS_PROP_ICID(string_buf);
	aux_data->aux_index = xc->index;
	aux_data->aux_name = (unsigned char *)xc->utfname;
	aux_data->aux_name_length = strlen(xc->classname)*sizeof(CARD16);

	/* int values */
	aux_data->integer_count = XS_PROP_INT_COUNT(string_buf);

	if (aux_data->integer_count > 0) {
		aux_data->integer_list = (int *)XS_PROP_INT_LIST(string_buf);
	} else {
		aux_data->integer_list = NULL;
	}

	/* string values */
	aux_data->string_count = XS_PROP_STR_COUNT(string_buf);
    	if (aux_data->string_count > 0) {
		unsigned char * prop_str = XS_PROP_STR_LIST(string_buf);

		if ((aux_data->string_list = (aux_string_t *)calloc(aux_data->string_count, 
						sizeof (aux_string_t))) == NULL) {
			free ((char *)aux_data);
			return NULL;
		}

		for (i = 0; i < aux_data->string_count; i++) {
			int	j;
			int	pn;
			size_t	len;

			len = *((CARD16 *)(prop_str));
			prop_str += sizeof (CARD16);
			aux_data->string_list[i].ptr = prop_str;
			aux_data->string_list[i].length = len;
			prop_str += len;
			pn = padding[(sizeof(CARD16) + len) % 4];
			for (j = 0; j < pn; j++) {
				*prop_str++ = 0U;
			}
		}
	} else {
		aux_data->string_list = NULL;
	}

	aux_data->string_ptr = NULL;

	return aux_data;
}

/************************************************************
    Send messages to aux modules
************************************************************/
Bool xaux_object_send_message(
	aux_t *		aux,
	xaux_class_t *	xc,
	int		im_id,
	int		ic_id,
	aux_data_type_t	type,
	Atom		atom_client,
	Atom		atom_data)
{
	Display *		display;
	XClientMessageEvent	event;

	display = aux->service->display(aux);

	if (xaux_object_get_extwin(xc, display) == False)
		return False;

	event.type = ClientMessage;
	event.serial = 0;
	event.send_event = True;
	event.display = display;
	event.window = xc->extwin;
	event.message_type = atom_client;
	event.format = 32;

	event.data.l[0] = xc->atom_classname;
	event.data.l[1] = ((CARD32)im_id << 16) | ((CARD32)ic_id & 0xffff);
	event.data.l[2] = xc->index;
	event.data.l[3] = type; /* CREATE, DONE, ... */
	event.data.l[4] = atom_data;

	XSendEvent(display, xc->extwin, True, 0, (XEvent *)(&event));

	/* needed in en_US.UTF-8 */
        XFlush(display);

	return True;
}

Bool xaux_object_send_property(
	aux_t *			aux,
	xaux_class_t *		xc,
	const unsigned char *	p,
	int			len,
	Atom			atom_client)
{
	Display *	display;
	int		i = 1;
	Bool		rv;
	Window		win;

	display = aux->service->display(aux);

	if (xaux_object_get_extwin(xc, display) == False)
		return False;

	win = xc->extwin;

	XChangeProperty(display, win,
		xc->atom_sx[xc->atom_sx_idx], XA_STRING,
		8, PropModeReplace, (unsigned char *)p, len);

	if (xaux_object_send_message(aux, xc,
		aux->service->im_id(aux), aux->service->ic_id(aux),
		AUX_DATA_DRAW, atom_client, xc->atom_sx[xc->atom_sx_idx]) == False) {
			return False;
	}

	/* XFlush() has been called in xaux_object_send_message() */
	
	if (++xc->atom_sx_idx == XAUX_SX_NATOMS)
		xc->atom_sx_idx = 1;
	
	return True;
}

Bool xaux_object_draw_aux_module(xaux_object_t *xaux_object, aux_t *aux, 
char *classname, aux_data_t *aux_data, int type)
{
	char *		string_buf;
	int 		string_len;
	Bool		rv = True;
	Display *	display;
	Atom 		atom_client;

	xaux_class_t *	xc;

	DEBUG_printf("aux_tmp: %p, aux_setvalue: %p, compose: %p\n", &aux_tmp, aux_setvalue, compose);

	display = aux->service->display(aux);

	/* make the aux_object as the current focused aux object */
	XSetSelectionOwner(display,
		xaux_object->atom_classname, xaux_object->window, CurrentTime);

	xc = xaux_object_get_class_from_name(xaux_object, classname);
	DEBUG_printf("xaux_object_get_class_from_name, return: xc: %p\n", xc);
	if (xc == NULL)
		return False;

	if (xaux_object_get_extwin(xc, display) == False)
		return False;

	/* workaround for "stored reference to aux_t is corrupred" problem */
	aux_tmp.ic = aux->ic;
	aux_setvalue = aux->service->aux_setvalue;
	compose = aux->service->compose;

	atom_client = XInternAtom(display, classname, True);

	if (type == AUX_DATA_START || type == AUX_DATA_DONE) {
		rv = xaux_object_send_message(aux, xc, aux_data->im, aux_data->ic, type, atom_client, (Atom)None);
	} else if (type == AUX_DATA_DRAW) {
		string_buf = (char *)xaux_object_compose_aux_data(aux, xc, aux_data, type, &string_len);

		DEBUG_printf("so_Draw[%s] im:0x%x ic:0x%x in=%d sn=%d\n",
			xc->classname, aux_data->im, aux_data->ic,
			aux_data->integer_count, aux_data->string_count);
		DEBUG_printf("total = %d\n", string_len);

		if (string_buf != NULL) {
			rv = xaux_object_send_property(aux, xc,
				(unsigned char *)string_buf, string_len, atom_client);
			free(string_buf);
		}
	}

	return (rv);
}

/************************************************************
    Receive messages from aux modules
************************************************************/
static Bool
xaux_object_process_property_update(
	Display	*		display,
	Window			window,
	Atom			atom,
	xaux_class_t *		xc)
{
	Atom		actual_type_return;
	int		actual_format_return;
	unsigned long	nitem_return;
	unsigned long	bytes_after_return;
	unsigned char	*prop_return;
	int		r;
	char		*atom_name;
	aux_data_t	*aux_data;
	int		size;
	unsigned char   *p;

	atom_name = XGetAtomName (display, atom);
	DEBUG_printf("xaux_object_process_property_update: atom_name: %s\n", atom_name);
	DEBUG_printf("aux_tmp: %p, aux_setvalue: %p, compose: %p\n", &aux_tmp, aux_setvalue, compose);

	r = XGetWindowProperty(display, window,
			       atom, 0, INT_MAX, False,
			       AnyPropertyType, &actual_type_return,
			       &actual_format_return, &nitem_return,
			       &bytes_after_return, &prop_return);

	if (r != Success) {
		return False;
	}

	aux_data = xaux_object_decompose_from_string(xc, prop_return);
	if (aux_data == NULL) {
		XFree(prop_return);
		return False;
	}

	/* compose and send message to engine */

	/* workaround for "stored reference to aux_t is corrupred" problem */
	if ((p = /*ic->aux->service->*/compose(aux_data, &size)) == NULL) {
		free(aux_data->string_list);
		free(aux_data);
		XFree(prop_return);
		return False;
	}

	DEBUG_printf("so_SetValue[%s] im:0x%x ic:0x%x in=%d sn=%d\n",
			xc->classname, aux_data->im, aux_data->ic,
			aux_data->integer_count, aux_data->string_count);

	/* workaround for "stored reference to aux_t is corrupred" problem */
	/*ic->aux->service->*/aux_setvalue(/*ic->aux*/&aux_tmp, p, size);

	free(p);
	free(aux_data->string_list);
	free(aux_data);

	XFree(prop_return);

	return True;
}

static Bool xaux_object_process_client_message(Display *display, Window window, XClientMessageEvent *event)
{
	aux_data_t	aux_data_;
	aux_data_t	*aux_data = &(aux_data_);
	aux_data_type_t	type;
	xaux_class_t	*xc;

  	xc = &xaux_classes[0];

	aux_data->im = ((CARD32)(event->data.l[1])) >> 16;
	aux_data->ic = ((CARD32)(event->data.l[1])) & 0xffff;
	aux_data->aux_index = (CARD32)(event->data.l[2]);

	type = (CARD32)(event->data.l[3]);

	switch (type) {
	case AUX_DATA_SETVALUE:
		return xaux_object_process_property_update(display, window,
				(Atom)(event->data.l[4]), xc);
	default:
		return False;
	}
}

static Bool xaux_object_event_filter(Display *display, Window window, XEvent *event, XPointer pointer)
{
	switch (event->type) {
	case ClientMessage:
		return (xaux_object_process_client_message(display, window,
			(XClientMessageEvent *)event));
	}
	return False;
}
